/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "log.h"
#include "dxrendersystem.h"
#include "dxrenderwindow.h"
#include "dxmappings.h"
#include "light.h"
#include "rendertarget.h"
#include "dxrenderdata.h"
#include "dxindexbuffer.h"
#include "dxvertexbuffer.h"
#include "dxvertexdeclaration.h"
#include "dxrenderdata.h"
#include "dxtexture.h"

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	/**********************************************************************************************************/
	DWORD FLOAT2DWORD(REAL f)
	{
		float temp = static_cast<float>(f);
		DWORD* p = reinterpret_cast<DWORD*>(&temp);
		return *p;
	}
	/**********************************************************************************************************/
	RenderSystem* DXRenderSystemFactory::CreateInstance( const std::string& )
	{
		return new DXRenderSystem();
	}
	/**********************************************************************************************************/
	template<> DXRenderSystem* Singleton<DXRenderSystem>::m_singleton = 0;
	/**********************************************************************************************************/
	DXRenderSystem::DXRenderSystem()
		:m_deviceLost(false)
		,m_primaryWindow(NULL)
		,m_currentLights(0)
	{
		m_direct3d.d3dInterface = ComPtr<IDirect3D9>(Direct3DCreate9(D3D_SDK_VERSION));
		if(m_direct3d.d3dInterface.IsNull())
			throw std::logic_error("DirectX 9 doesn't exists");
	}
	/**********************************************************************************************************/
	DXRenderSystem::~DXRenderSystem()
	{
		ShutDown();
	}
	/**********************************************************************************************************/
	RenderWindowPtr DXRenderSystem::CreateRenderWindow(const std::string& name, const PresentParams& pp)
	{
		DXRenderWindow* win = new DXRenderWindow(&m_direct3d);
		RefPtr<RenderWindow, WeakPtrExtension> rendtarg(win);

		win->Create(name, pp);

		if(m_primaryWindow == NULL)
			m_primaryWindow = win;
		else
			m_secondaryWindows.push_back(win);

		AttachRenderTarget(RefPtr<RenderTarget, WeakPtrExtension>(rendtarg));

		Log::GetSingleton() << "DXRenderWindow created : " << name << std::endl;
		return rendtarg;
	}
	/**********************************************************************************************************/
	void DXRenderSystem::ClearFrameBuffer(unsigned int buffers, const Color& color, REAL depth, unsigned short stencil)
	{
		DWORD flags = 0;
        if (buffers & FBT_COLOUR)
        {
            flags |= D3DCLEAR_TARGET;
        }
        if (buffers & FBT_DEPTH)
        {
            flags |= D3DCLEAR_ZBUFFER;
        }
        if (buffers & FBT_STENCIL )
        {
            flags |= D3DCLEAR_STENCIL;
        }
        HRESULT hr;
        if( FAILED( hr = m_direct3d.d3dDevice->Clear(
            0,
            NULL,
            flags,
            color.GetAsARGB(),
            static_cast<float>(depth),
            stencil ) ) )
        {

			throw std::logic_error("Error clearing frame buffer : D3D9RenderSystem::clearFrameBuffer" );
        }
	}
	/**********************************************************************************************************/
	void DXBuffersVisitor::Visit(DXRenderWindow* window)
	{
		backBufferSurface = window->GetBackBuffer();
		zBufferSurface = window->GetZBuffer();
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetRenderTarget(RenderTarget* renderTarget)
	{
		Assert(renderTarget);

		DXBuffersVisitor rtv;
		renderTarget->Accept(rtv);

		if(rtv.backBufferSurface)
			m_direct3d.d3dDevice->SetRenderTarget(0,rtv.backBufferSurface);
		else
			throw std::invalid_argument("Can't set backbuffer for render target");

		if(rtv.zBufferSurface)
			m_direct3d.d3dDevice->SetDepthStencilSurface(rtv.zBufferSurface);
		else
			throw std::invalid_argument("Can't set z buffer render target");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::NotifyDeviceLost()
	{
		m_deviceLost = true;
	}
	/**********************************************************************************************************/
	void DXRenderSystem::RestoreLostDevice()
	{
		StartResetSignal();

		// release additional swap chains (secondary windows)
		SecondaryWindowList::iterator sw;
		for (sw = m_secondaryWindows.begin(); sw != m_secondaryWindows.end(); ++sw)
		{
			(*sw)->DestroyD3DResources();
		}

		D3DPRESENT_PARAMETERS presParams;
		m_primaryWindow->GetPresentationParameters(&presParams);

		HRESULT hr = m_direct3d.d3dDevice->Reset(&presParams);

		if (hr == D3DERR_DEVICELOST)
			return;
		else if (FAILED(hr))
			throw std::logic_error("Cannot reset device!");

		// recreate additional swap chains
		for (sw = m_secondaryWindows.begin(); sw != m_secondaryWindows.end(); ++sw)
		{
			(*sw)->CreateD3DResources();
		}

		EndResetSignal();
		m_deviceLost = false;
		Log::GetSingleton() << "DXRenderSystem restored" << std::endl;
	}
	/**********************************************************************************************************/
	void DXRenderSystem::BeginFrame()
	{
		if( FAILED( m_direct3d.d3dDevice->BeginScene() ) )
			throw std::logic_error("Error beginning frame DXRenderSystem::BeginFrame");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::EndFrame()
	{
		if( FAILED( m_direct3d.d3dDevice->EndScene() ) )
			throw std::logic_error("Error ending frame DXRenderSystem::_endFrame" );
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetPolygonMode(PolygonMode level)
	{
		if (FAILED(SetRenderState(D3DRS_FILLMODE, DXMappings::Get(level))))
			throw std::logic_error("Error setting polygon mode DXRenderSystem::SetPolygonMode");

	}
	/**********************************************************************************************************/
	HRESULT DXRenderSystem::SetRenderState(D3DRENDERSTATETYPE state, DWORD value)
	{
		HRESULT hr;
		DWORD oldVal;

		if ( FAILED( hr = m_direct3d.d3dDevice->GetRenderState(state, &oldVal) ) )
			return hr;
		if ( oldVal == value )
			return D3D_OK;
		else
			return m_direct3d.d3dDevice->SetRenderState(state, value);
	}
	/**********************************************************************************************************/
	HRESULT DXRenderSystem::SetSamplerState( DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value)
	{
		HRESULT hr;
		DWORD oldVal;

		if ( FAILED( hr = m_direct3d.d3dDevice->GetSamplerState(Sampler,Type, &oldVal) ) )
			return hr;
		if ( oldVal == Value )
			return D3D_OK;
		else
			return m_direct3d.d3dDevice->SetSamplerState(Sampler, Type, Value);
	}
	/**********************************************************************************************************/
	HRESULT DXRenderSystem::SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value)
	{
		HRESULT hr;
		DWORD oldVal;

		if ( FAILED( hr = m_direct3d.d3dDevice->GetTextureStageState(Stage,Type, &oldVal) ) )
			return hr;
		if ( oldVal == Value )
			return D3D_OK;
		else
			return m_direct3d.d3dDevice->SetTextureStageState(Stage, Type, Value);
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetViewMatrix(const Matrix4<REAL> &m)
	{
		D3DXMATRIX d3dmat = DXMappings::MakeD3DXMatrix( m );

		if( FAILED( m_direct3d.d3dDevice->SetTransform( D3DTS_VIEW, &d3dmat ) ) )
			throw std::logic_error("Cannot set D3D9 view matrix DXRenderSystem::SetViewMatrix" );

	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetProjectionMatrix(const Matrix4<REAL> &m)
	{
		D3DXMATRIX d3dmat = DXMappings::MakeD3DXMatrix( m );

		if( FAILED( m_direct3d.d3dDevice->SetTransform( D3DTS_PROJECTION, &d3dmat ) ) )
			throw std::logic_error("Cannot set D3D9 proj matrix DXRenderSystem::SetProjectionMatrix" );
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetWorldMatrix(const Matrix4<REAL> &m)
	{
		D3DXMATRIX d3dMat = DXMappings::MakeD3DXMatrix( m );

		if( FAILED( m_direct3d.d3dDevice->SetTransform( D3DTS_WORLD, &d3dMat ) ) )
			throw std::logic_error("Cannot set D3D9 world matrix. DXRenderSystem::SetWorldMatrix" );
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetAmbientLight(const Color& color)
	{
		HRESULT hr = SetRenderState( D3DRS_AMBIENT, D3DCOLOR_COLORVALUE( color.r, color.g, color.b, 1.0f ) );
		if( FAILED( hr ) )
			throw std::logic_error("Failed to set render stat D3DRS_AMBIENT DXRenderSystem::SetAmbientLight" );
	}
	/**********************************************************************************************************/
	void DXRenderSystem::UseLights(const LightVector& lights)
	{
		LightVector::const_iterator i, iend;
        iend = lights.end();
        unsigned short num = 0;
        for (i = lights.begin(); i != iend; ++i, ++num)
        {
            SetD3D9Light(num, i->GetPointer());
        }
        // Disable extra lights
        for (; num < m_currentLights; ++num)
        {
            SetD3D9Light(num, NULL);
        }
        m_currentLights = lights.size();
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetD3D9Light( size_t index,const Light* lt )
	{
		HRESULT hr;

		D3DLIGHT9 d3dLight;
		ZeroMemory( &d3dLight, sizeof(d3dLight) );

        if (!lt)
        {
            if( FAILED( hr = m_direct3d.d3dDevice->LightEnable( index, FALSE) ) )
				throw std::logic_error("Unable to disable light. DXRenderSystem::setD3D9Light");
        }
        else
        {
			switch( lt->GetType() )
			{
			case Light::LT_POINT:
				d3dLight.Type = D3DLIGHT_POINT;
				break;

			case Light::LT_DIRECTIONAL:
				d3dLight.Type = D3DLIGHT_DIRECTIONAL;
				break;

			case Light::LT_SPOTLIGHT:
				d3dLight.Type = D3DLIGHT_SPOT;
				d3dLight.Falloff = static_cast<FLOAT>(lt->GetSpotlightFalloff());
				d3dLight.Theta = static_cast<FLOAT>(lt->GetSpotlightInnerAngle());
				d3dLight.Phi = static_cast<FLOAT>(lt->GetSpotlightOuterAngle());
				break;
			}

			Color col;
			col = lt->GetDiffuseColor();
			d3dLight.Diffuse = D3DXCOLOR( col.r, col.g, col.b, col.a );

			col = lt->GetSpecularColour();
			d3dLight.Specular = D3DXCOLOR( col.r, col.g, col.b, col.a );

			Vector3<REAL> vec;
			if( lt->GetType() != Light::LT_DIRECTIONAL || lt->GetType() != Light::LT_POINT)
			{
				vec = lt->GetPosition();
				d3dLight.Position = D3DXVECTOR3( static_cast<FLOAT>(vec.x), static_cast<FLOAT>(vec.y), static_cast<FLOAT>(vec.z) );
			}

			d3dLight.Range = static_cast<FLOAT>(lt->GetAttenuationRange());
			d3dLight.Attenuation0 = static_cast<FLOAT>(lt->GetAttenuationConstant());
			d3dLight.Attenuation1 = static_cast<FLOAT>(lt->GetAttenuationLinear());
			d3dLight.Attenuation2 = static_cast<FLOAT>(lt->GetAttenuationQuadric());

			if( FAILED( hr = m_direct3d.d3dDevice->SetLight( index, &d3dLight ) ) )
				throw std::logic_error("Unable to set light details. DXRenderSystem::SetD3D9Light" );

            if( FAILED( hr = m_direct3d.d3dDevice->LightEnable( index, TRUE ) ) )
			   throw std::logic_error("Unable to enable light.D3D9RenderSystem::setD3D9Light" );
        }
	}
	/**********************************************************************************************************/
	void DXRenderSystem::Render(RenderDataPtr renderData)
	{

		// Exit immediately if there is nothing to render
		if (renderData->GetVertexCount() == 0 || renderData->GetPrimitiveCount() == 0)
			return;

		// Call super class
		RenderSystem::Render(renderData);

		DXRenderData* dxRenderData = static_cast<DXRenderData*>(renderData.GetPointer());

		if (FAILED(m_direct3d.d3dDevice->SetVertexDeclaration(dxRenderData->GetVertexDeclaration())))
			throw std::logic_error("Unable to set D3D9 vertex declaration DXRenderSystem::Render");

		if (FAILED(m_direct3d.d3dDevice->SetStreamSource(0,dxRenderData->GetVertexBuffer(), 0, dxRenderData->GetVertexSize())))
			throw std::logic_error("Unable to set D3D9 stream source for buffer binding DXRenderSystem::Render");

		// do indexed draw operation
		if(dxRenderData->GetPrimitiveType() == D3DPT_TRIANGLELIST)
		{
			if(FAILED(m_direct3d.d3dDevice->SetIndices( dxRenderData->GetIndexBuffer())))
				throw std::logic_error("Failed to set index buffer DXRenderSystem::Render" );

			if(dxRenderData->GetPrimitiveCount() > 0)
			{
				if(FAILED(m_direct3d.d3dDevice->DrawIndexedPrimitive(
					dxRenderData->GetPrimitiveType(),
					dxRenderData->GetBaseVertex(),
					dxRenderData->GetStartVertex(),
					dxRenderData->GetVertexCount(),
					dxRenderData->GetStartIndex(),
					dxRenderData->GetPrimitiveCount()
					)))
					throw std::logic_error("Failed to DrawIndexedPrimitive DXRenderSystem::Render" );
			}
		}
		else if(dxRenderData->GetPrimitiveType() == D3DPT_POINTLIST)
		{
			if(dxRenderData->GetPrimitiveCount() > 0)
			{
				if(FAILED(m_direct3d.d3dDevice->DrawPrimitive(
					dxRenderData->GetPrimitiveType(),
					dxRenderData->GetStartVertex(),
					dxRenderData->GetPrimitiveCount()
					)))
					throw std::logic_error("Failed to DrawPrimitive DXRenderSystem::Render" );
			}
		}
	}
	/**********************************************************************************************************/
	RefPtr<HardWareBuffer,WeakPtrExtension> DXRenderSystem::CreateIndexBuffer(unsigned int facecount, IndexType type)
	{
		IDirect3DIndexBuffer9* indexBuffer = 0;
		D3DFORMAT format = DXMappings::Get(type);
		if(FAILED(m_direct3d.d3dDevice->CreateIndexBuffer(facecount * sizeof(unsigned int) * 3,0,format,D3DPOOL_MANAGED,&indexBuffer,NULL)))
			throw std::logic_error("Can't create dx9 index buffer");
		RefPtr<HardWareBuffer,WeakPtrExtension> dxbuf( new DXIndexBuffer(indexBuffer));
		Log::GetSingleton() << "DXIndexBuffer created" << std::endl;
		return dxbuf;
	}
	/**********************************************************************************************************/
	RefPtr<HardWareBuffer, WeakPtrExtension> DXRenderSystem::CreateVertexBuffer(unsigned int vertexCount, unsigned int vertexSize)
	{
		IDirect3DVertexBuffer9* vertexBuffer = 0;
		if(FAILED(m_direct3d.d3dDevice->CreateVertexBuffer(vertexCount * vertexSize, 0, 0, D3DPOOL_MANAGED, &vertexBuffer, NULL)))
			throw std::logic_error("Can't create dx9 vertex buffer");
		RefPtr<HardWareBuffer,WeakPtrExtension> dxbuf( new DXVertexBuffer(vertexBuffer));
		Log::GetSingleton() << "DXVertexBuffer created" << std::endl;
		return dxbuf;
	}
	/**********************************************************************************************************/
	RefPtr<VertexDeclaration, WeakPtrExtension> DXRenderSystem::CreateVertexDeclaration()
	{
		return RefPtr<VertexDeclaration, WeakPtrExtension>(new DXVertexDeclaration(this));
	}
	/**********************************************************************************************************/
	RefPtr<RenderData, WeakPtrExtension>  DXRenderSystem::CreateRenderData(unsigned int faceCount, unsigned int vertexCount, VertexDeclarationPtr vertexDeclaration, HardWareBufferPtr indexData, HardWareBufferPtr vertexData, PrimitiveType primitiveType)
	{
		if(!vertexData.IsNull())
		{
			if((primitiveType == PT_TRIANGLELIST && !indexData.IsNull()) || primitiveType == PT_POINTLIST)
			{
				IDirect3DVertexDeclaration9* vdecl = static_cast<DXVertexDeclaration*>(vertexDeclaration.GetPointer())->GetVertexDeclaration();
				UINT vertexSize = vertexDeclaration->GetVertexSize(0);
				IDirect3DVertexBuffer9* vertexBuffer = static_cast<DXVertexBuffer*>(vertexData.GetPointer())->GetBuffer();
				INT baseVertex = 0;
				UINT startVertex = 0;
				UINT startIndex = 0;
				IDirect3DIndexBuffer9* indexBuffer = 0;
				if(primitiveType == PT_TRIANGLELIST)
					indexBuffer = static_cast<DXIndexBuffer*>(indexData.GetPointer())->GetBuffer();

				return RefPtr<RenderData, WeakPtrExtension>(new DXRenderData(vdecl,vertexSize,vertexBuffer,indexBuffer,baseVertex,startVertex,startIndex,faceCount,vertexCount,DXMappings::Get(primitiveType)));
			}
		}
		return RefPtr<RenderData, WeakPtrExtension>();
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetDepthEnable(bool val)
	{
		if(FAILED(SetRenderState( D3DRS_ZENABLE, val ? D3DZB_TRUE : D3DZB_FALSE)))
			throw std::logic_error("Error setting depth buffer test state");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetDepthWriteEnable(bool val)
	{
		if( FAILED( SetRenderState( D3DRS_ZWRITEENABLE, val ? TRUE : FALSE ) ) )
			throw std::logic_error("Error setting depth buffer write state");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetMaterial(const Color& ambient, const Color& diffuse, const Color& emissive, const Color& specular, REAL power)
	{
		D3DMATERIAL9 material;
		material.Diffuse = D3DXCOLOR( diffuse.r, diffuse.g, diffuse.b, diffuse.a );
		material.Ambient = D3DXCOLOR( ambient.r, ambient.g, ambient.b, ambient.a );
		material.Specular = D3DXCOLOR( specular.r, specular.g, specular.b, specular.a );
		material.Emissive = D3DXCOLOR( emissive.r, emissive.g, emissive.b, emissive.a );
		material.Power = static_cast<float>(power);

		if(FAILED(m_direct3d.d3dDevice->SetMaterial( &material )))
			throw std::logic_error("Error setting D3D material");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetShadeMode(ShadeMode val)
	{
		if(FAILED(SetRenderState( D3DRS_SHADEMODE, DXMappings::Get(val) )))
			throw std::logic_error("Failed to set render stat D3DRS_SHADEMODE");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetCullMode(FaceCullMode val)
	{
		if( FAILED (SetRenderState(D3DRS_CULLMODE, DXMappings::Get(val))))
			throw std::logic_error("Failed to set render stat D3DRS_CULLMODE");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetLightingEnable(bool val)
	{
		if( FAILED( SetRenderState( D3DRS_LIGHTING, val ) ) )
			throw std::logic_error("Failed to set render state D3DRS_LIGHTING");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetPointSpritesEnabled(bool enabled)
	{
		if( FAILED( SetRenderState(D3DRS_POINTSPRITEENABLE, enabled ? TRUE : FALSE)))
			throw std::logic_error("Failed to set render state D3DRS_POINTSPRITEENABLE");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetPointParameters(REAL size, bool attenuationEnabled,REAL constant, REAL linear, REAL quadratic, REAL minSize, REAL maxSize)
	{
		if(attenuationEnabled)
		{
			// scaling required
			SetRenderState(D3DRS_POINTSCALEENABLE, TRUE);
			SetRenderState(D3DRS_POINTSCALE_A, FLOAT2DWORD(constant));
			SetRenderState(D3DRS_POINTSCALE_B, FLOAT2DWORD(linear));
			SetRenderState(D3DRS_POINTSCALE_C, FLOAT2DWORD(quadratic));
		}
		else
		{
			// no scaling required
			SetRenderState(D3DRS_POINTSCALEENABLE, FALSE);
		}
		SetRenderState(D3DRS_POINTSIZE, FLOAT2DWORD(size));
		SetRenderState(D3DRS_POINTSIZE_MIN, FLOAT2DWORD(minSize));
		if (maxSize == 0.0f)
			maxSize = m_direct3d.d3dCaps.MaxPointSize;
		SetRenderState(D3DRS_POINTSIZE_MAX, FLOAT2DWORD(maxSize));
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTexture(size_t unit, TexturePtr texPtr)
	{
		if(texPtr.IsNull())
		{
			if(FAILED(m_direct3d.d3dDevice->SetTexture(unit, 0)))
				throw std::logic_error("Failed to reset texture");
		}
		else
		{
			DXTexture* dxtex = static_cast<DXTexture*>(texPtr.GetPointer());
			if(FAILED(m_direct3d.d3dDevice->SetTexture(unit, dxtex->GetTexture())))
				throw std::logic_error("Failed to set  texture");
		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureCoordSet(size_t unit, TexCoordCalcMethod m, size_t index)
	{
		if(FAILED(SetTextureStageState( unit, D3DTSS_TEXCOORDINDEX, DXMappings::Get(m, m_direct3d.d3dCaps) | index )))
			throw std::logic_error("Failed to set render state D3DTSS_TEXCOORDINDEX");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureBlendMode(size_t unit, const LayerBlendModeEx& bm)
	{
		D3DTEXTURESTAGESTATETYPE tss;
		D3DCOLOR manualD3D;

		// choose type of blend.
		if( bm.blendType == LBT_COLOUR )
			tss = D3DTSS_COLOROP;
		else if( bm.blendType == LBT_ALPHA )
			tss = D3DTSS_ALPHAOP;
		else
			throw std::invalid_argument("Invalid blend type");

		// set manual factor if required by operation
		if (bm.operation == LBX_BLEND_MANUAL)
		{
			if(FAILED(SetRenderState( D3DRS_TEXTUREFACTOR, D3DXCOLOR(0.0, 0.0, 0.0,  static_cast<FLOAT>(bm.factor)) )))
				throw std::logic_error("Failed to set manual factor");
		}
		// set operation
		if(FAILED(SetTextureStageState( unit, tss, DXMappings::Get(bm.operation, m_direct3d.d3dCaps) )))
			throw std::logic_error("Failed to set operation");

		// choose source 1
		if( bm.blendType == LBT_COLOUR )
		{
			tss = D3DTSS_COLORARG1;
			manualD3D = D3DXCOLOR( bm.colorArg1.r, bm.colorArg1.g, bm.colorArg1.b, bm.colorArg1.a );
			m_manualBlendColours[unit][0] = bm.colorArg1;
		}
		else if( bm.blendType == LBT_ALPHA )
		{
			tss = D3DTSS_ALPHAARG1;
			manualD3D = D3DXCOLOR( m_manualBlendColours[unit][0].r, m_manualBlendColours[unit][0].g, m_manualBlendColours[unit][0].b, static_cast<FLOAT>(bm.alphaArg1) );
		}
		else
		{
			throw std::logic_error("Invalid blend type");
		}
		// Set manual factor if required
		if (bm.source1 == LBS_MANUAL)
		{
			// Global state
			if(FAILED(SetRenderState( D3DRS_TEXTUREFACTOR, manualD3D )))
				throw std::logic_error("Failed to set manual factor");
		}
		// set source 1
		if(FAILED(SetTextureStageState( unit, tss, DXMappings::Get(bm.source1) )))
			throw std::logic_error("Failed to set source1");

		// choose source 2
		if( bm.blendType == LBT_COLOUR )
		{
			tss = D3DTSS_COLORARG2;
			manualD3D = D3DXCOLOR( bm.colorArg2.r, bm.colorArg2.g, bm.colorArg2.b, bm.colorArg2.a );
			m_manualBlendColours[unit][1] = bm.colorArg1;
		}
		else if( bm.blendType == LBT_ALPHA )
		{
			tss = D3DTSS_ALPHAARG2;
			manualD3D = D3DXCOLOR( m_manualBlendColours[unit][1].r,	m_manualBlendColours[unit][1].g, m_manualBlendColours[unit][1].b, static_cast<FLOAT>(bm.alphaArg2) );
		}
		// Set manual factor if required
		if (bm.source2 == LBS_MANUAL)
		{

			if(FAILED(SetRenderState( D3DRS_TEXTUREFACTOR, manualD3D )))
				throw std::logic_error("Failed to set manual factor");
		}
		// Now set source 2
		if(FAILED(SetTextureStageState( unit, tss, DXMappings::Get(bm.source2) )))
			throw std::logic_error("Failed to set source 2");

		// Set interpolation factor if lerping
		if (bm.operation == LBX_BLEND_DIFFUSE_COLOUR && m_direct3d.d3dCaps.TextureOpCaps & D3DTEXOPCAPS_LERP)
		{
			// choose source 0 (lerp factor)
			if( bm.blendType == LBT_COLOUR )
			{
				tss = D3DTSS_COLORARG0;
			}
			else if( bm.blendType == LBT_ALPHA )
			{
				tss = D3DTSS_ALPHAARG0;
			}
			if(FAILED(SetTextureStageState(unit, tss, D3DTA_DIFFUSE)))
				throw std::logic_error("Failed to set lerp source 0");

		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureUnitFiltering(size_t unit, FilterType ftype, FilterOptions filter)
	{
		if (FAILED(SetSamplerState( unit, DXMappings::Get(ftype), DXMappings::Get(filter))))
			throw std::logic_error("Failed to set texture filter");
	}
	/**********************************************************************************************************/
	unsigned int DXRenderSystem::GetCurrentAnisotropy(size_t unit)
	{
		DWORD oldVal;
		if(FAILED(m_direct3d.d3dDevice->GetSamplerState(unit, D3DSAMP_MAXANISOTROPY, &oldVal)))
			throw std::logic_error("Failed to get texture Anisotropy");
		return oldVal;
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureLayerAnisotropy(size_t unit, unsigned int maxAnisotropy)
	{
		if (static_cast<DWORD>(maxAnisotropy) > m_direct3d.d3dCaps.MaxAnisotropy)
			maxAnisotropy = m_direct3d.d3dCaps.MaxAnisotropy;

		if (GetCurrentAnisotropy(unit) != maxAnisotropy)
		{
			if(FAILED(SetSamplerState( unit, D3DSAMP_MAXANISOTROPY, maxAnisotropy )))
				throw std::logic_error("Failed to set texture Anisotropy");
		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureAddressingMode(size_t unit, const UVWAddressingMode& uvw)
	{
		if( FAILED( SetSamplerState( unit, D3DSAMP_ADDRESSU, DXMappings::Get(uvw.u, m_direct3d.d3dCaps) ) ) )
			throw std::logic_error("Failed to set texture addressing mode for U");
		if( FAILED( SetSamplerState( unit, D3DSAMP_ADDRESSV, DXMappings::Get(uvw.v, m_direct3d.d3dCaps) ) ) )
			throw std::logic_error("Failed to set texture addressing mode for V");
		if( FAILED( SetSamplerState( unit, D3DSAMP_ADDRESSW, DXMappings::Get(uvw.w, m_direct3d.d3dCaps) ) ) )
			throw std::logic_error("Failed to set texture addressing mode for W");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureBordercolor(size_t unit, const Color& color)
	{
		if( FAILED( SetSamplerState( unit, D3DSAMP_BORDERCOLOR, color.GetAsARGB()) ) )
			throw std::logic_error("Failed to set texture border colour");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureMipmapBias(size_t unit, REAL bias)
	{
		if (m_direct3d.d3dCaps.RasterCaps & D3DPRASTERCAPS_MIPMAPLODBIAS)
		{
			// ugh - have to pass float data through DWORD with no conversion
			if(FAILED(SetSamplerState(unit, D3DSAMP_MIPMAPLODBIAS, FLOAT2DWORD(bias))))
				throw std::logic_error("Unable to set texture mipmap bias");
		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetTextureMatrix(size_t unit, const Matrix4<REAL>& xform, TexCoordDim texCoordDim)
	{
		D3DXMATRIX d3dMat = DXMappings::MakeD3DXMatrix(xform);
		if(!D3DXMatrixIsIdentity(&d3dMat))
		{

			if(FAILED(SetTextureStageState( unit, D3DTSS_TEXTURETRANSFORMFLAGS,  DXMappings::Get(texCoordDim))))
				throw std::logic_error("Unable to set texture coord. dimention");

			if(FAILED(m_direct3d.d3dDevice->SetTransform( (D3DTRANSFORMSTATETYPE)(D3DTS_TEXTURE0 + unit), &d3dMat )))
				throw std::logic_error("Unable to set texture matrix");
		}
		else
		{
			// disable all of this
			if(FAILED(SetTextureStageState( unit, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE )))
				throw std::logic_error("Unable to disable texture coordinate transform");
		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetSceneBlending(SceneBlendFactor sourceFactor, SceneBlendFactor destFactor)
	{
		if( sourceFactor == SBF_ONE && destFactor == SBF_ZERO)
		{
			if (FAILED(SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE)))
				throw std::logic_error("Failed to set alpha blending option");
		}
		else
		{
			if (FAILED(SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE)))
				throw std::logic_error("Failed to set alpha blending option");
			if( FAILED( SetRenderState( D3DRS_SRCBLEND, DXMappings::Get(sourceFactor) ) ) )
				throw std::logic_error("Failed to set source blend");
			if( FAILED( SetRenderState( D3DRS_DESTBLEND, DXMappings::Get(destFactor) ) ) )
				throw std::logic_error("Failed to set destination blend");
		}
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetAlphaRejectSettings(CompareFunction func, unsigned char value)
	{
		if (func != CMPF_ALWAYS_PASS)
		{
			if( FAILED( SetRenderState( D3DRS_ALPHATESTENABLE,  TRUE ) ) )
				throw std::logic_error("Failed to enable alpha testing");
		}
		else
		{
			if( FAILED( SetRenderState( D3DRS_ALPHATESTENABLE,  FALSE ) ) )
				throw std::logic_error("Failed to disable alpha testing");
		}
		// Set always just be sure
		if( FAILED(SetRenderState( D3DRS_ALPHAFUNC, DXMappings::Get(func) ) ) )
			throw std::logic_error("Failed to set alpha reject function");
		if( FAILED( SetRenderState( D3DRS_ALPHAREF, value ) ) )
			throw std::logic_error("Failed to set render state D3DRS_ALPHAREF");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetDepthBufferFunction(CompareFunction func)
	{
		if( FAILED( SetRenderState( D3DRS_ZFUNC, DXMappings::Get(func) ) ) )
			throw std::logic_error("Error setting depth buffer test function");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetColorBufferWriteEnabled(bool red, bool green, bool blue, bool alpha)
	{
		DWORD val = 0;
		if (red)
			val |= D3DCOLORWRITEENABLE_RED;
		if (green)
			val |= D3DCOLORWRITEENABLE_GREEN;
		if (blue)
			val |= D3DCOLORWRITEENABLE_BLUE;
		if (alpha)
			val |= D3DCOLORWRITEENABLE_ALPHA;
		if(FAILED(SetRenderState(D3DRS_COLORWRITEENABLE, val)))
			throw std::logic_error("Error setting colour write enable flags");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetDepthBias(REAL constantBias, REAL slopeScaleBias)
	{
		if ((m_direct3d.d3dCaps.RasterCaps & D3DPRASTERCAPS_DEPTHBIAS) != 0)
		{
			// Negate bias since D3D is backward
			// D3D also expresses the constant bias as an absolute value, rather than
			// relative to minimum depth unit, so scale to fit
			constantBias = -constantBias / 250000.0f;
			if(FAILED(SetRenderState(D3DRS_DEPTHBIAS, FLOAT2DWORD(constantBias))))
				throw std::logic_error("Error setting constant depth bias");
		}

		if ((m_direct3d.d3dCaps.RasterCaps & D3DPRASTERCAPS_SLOPESCALEDEPTHBIAS) != 0)
		{
			// Negate bias since D3D is backward
			slopeScaleBias = -slopeScaleBias;
			if(FAILED(SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, FLOAT2DWORD(slopeScaleBias))))
				throw std::logic_error("Error setting slope scale depth bias");
		}

	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetFog(FogMode mode, const Color& color , REAL expDensity, REAL linearStart, REAL linearEnd )
	{
		HRESULT hr;

		D3DRENDERSTATETYPE fogType, fogTypeNot;

		if (m_direct3d.d3dCaps.RasterCaps & D3DPRASTERCAPS_FOGTABLE)
		{
			fogType = D3DRS_FOGTABLEMODE;
			fogTypeNot = D3DRS_FOGVERTEXMODE;
		}
		else
		{
			fogType = D3DRS_FOGVERTEXMODE;
			fogTypeNot = D3DRS_FOGTABLEMODE;
		}

		if( mode == FOG_NONE)
		{
			// just disable
			hr = SetRenderState(fogType, D3DFOG_NONE );
			hr = SetRenderState(D3DRS_FOGENABLE, FALSE);
		}
		else
		{
			// Allow fog
			hr = SetRenderState( D3DRS_FOGENABLE, TRUE );
			hr = SetRenderState( fogTypeNot, D3DFOG_NONE );
			hr = SetRenderState( fogType, DXMappings::Get(mode) );

			hr = SetRenderState( D3DRS_FOGCOLOR, color.GetAsARGB() );
			hr = SetRenderState( D3DRS_FOGSTART, FLOAT2DWORD(linearStart) );
			hr = SetRenderState( D3DRS_FOGEND, FLOAT2DWORD(linearEnd ));
			hr = SetRenderState( D3DRS_FOGDENSITY, FLOAT2DWORD(expDensity) );
		}

		if( FAILED( hr ) )
			throw std::logic_error("Error setting render state");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetStencilCheckEnabled(bool enabled)
	{
		if(FAILED(SetRenderState(D3DRS_STENCILENABLE, enabled)))
			throw std::logic_error("Error enabling / disabling stencilling.");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetStencilBufferParams(CompareFunction func ,unsigned int refValue, unsigned int mask,StencilOperation stencilFailOp ,StencilOperation depthFailOp,StencilOperation passOp,bool twoSidedOperation)
	{
        // 2-sided operation
        if (twoSidedOperation)
        {
            if ((m_direct3d.d3dCaps.StencilCaps & D3DSTENCILCAPS_TWOSIDED) != D3DSTENCILCAPS_TWOSIDED)
				throw std::logic_error("2-sided stencils are not supported");

            if(FAILED(SetRenderState(D3DRS_TWOSIDEDSTENCILMODE, TRUE)))
				throw std::logic_error("Error setting 2-sided stencil mode.");

			if(FAILED(SetRenderState(D3DRS_CCW_STENCILFAIL, DXMappings::Get(stencilFailOp))))
				throw std::logic_error("Error setting stencil fail operation (2-sided).");

            // depth fail op
            if(FAILED(SetRenderState(D3DRS_CCW_STENCILZFAIL, DXMappings::Get(depthFailOp))))
				throw std::logic_error("Error setting stencil depth fail operation (2-sided).");

            // pass op
            if(FAILED(SetRenderState(D3DRS_CCW_STENCILPASS, DXMappings::Get(passOp))))
				throw std::logic_error("Error setting stencil pass operation (2-sided).");
        }
        else
        {
            if(FAILED(SetRenderState(D3DRS_TWOSIDEDSTENCILMODE, FALSE)))
				throw std::logic_error("Error setting 1-sided stencil mode.");
        }

        // func
		if(FAILED(SetRenderState(D3DRS_STENCILFUNC, DXMappings::Get(func))))
			throw std::logic_error("Error setting stencil buffer test function.");

        // reference value
        if(FAILED(SetRenderState(D3DRS_STENCILREF, refValue)))
			throw std::logic_error("Error setting stencil buffer reference value.");

        // mask
        if(FAILED(SetRenderState(D3DRS_STENCILMASK, mask)))
			throw std::logic_error("Error setting stencil buffer mask.");

		// fail op
        if(FAILED(SetRenderState(D3DRS_STENCILFAIL, DXMappings::Get(stencilFailOp))))
			throw std::logic_error("Error setting stencil fail operation.");

        // depth fail op
        if(FAILED(SetRenderState(D3DRS_STENCILZFAIL, DXMappings::Get(depthFailOp))))
			throw std::logic_error("Error setting stencil depth fail operation.");

        // pass op
		if(FAILED(SetRenderState(D3DRS_STENCILPASS, DXMappings::Get(passOp))))
			throw std::logic_error("Error setting stencil pass operation.");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetNormaliseNormals(bool normalise)
	{
		 if(FAILED(SetRenderState(D3DRS_NORMALIZENORMALS, normalise ? TRUE : FALSE)))
			 throw std::logic_error("Error setting normalized normals.");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetClipPlane (unsigned short index, REAL A, REAL B, REAL C, REAL D)
	{
		float plane[4] = { static_cast<float>(A), static_cast<float>(B), static_cast<float>(C), static_cast<float>(D) };
        if(FAILED(m_direct3d.d3dDevice->SetClipPlane (index, plane)))
			throw std::logic_error("Error setting clip plane.");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetEenableClipPlane (unsigned short index, bool enable)
	{
		DWORD prev;
		m_direct3d.d3dDevice->GetRenderState(D3DRS_CLIPPLANEENABLE, &prev);
		if(FAILED(SetRenderState(D3DRS_CLIPPLANEENABLE, enable ?	(prev | (1 << index)) : (prev & ~(1 << index)))))
			throw std::logic_error("Error enabling clip plane.");
	}
	/**********************************************************************************************************/
	void DXRenderSystem::SetScissorTest(bool enabled, size_t left, size_t top,size_t right, size_t bottom)
	{
        if (enabled)
        {
            if (FAILED(SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE)))
                throw std::logic_error("Unable to enable scissor rendering state.");

            RECT rect;
            rect.left = left;
            rect.top = top;
            rect.bottom = bottom;
            rect.right = right;
            if (FAILED(m_direct3d.d3dDevice->SetScissorRect(&rect)))
                throw std::logic_error("Unable to set scissor rectangle.");
        }
        else
        {
            if (FAILED(SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE)))
                throw std::logic_error("Unable to disable scissor rendering state.");
        }
	}
	/**********************************************************************************************************/
}
